﻿using System;
using System.Linq;
using RayDen.Library.Components;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Frames;
using RayDen.Library.Entity.Scene;

namespace RayDen.SmallPT.Light
{
    public class MeshLight : ILight
    {

        public const float NormalModifier = -1.0f;
        public string LightName;
        internal TriangleMesh mesh;
        public RgbSpectrum gain;
        public SampledSpectrum spectra;

        private FastRandom rnd = new FastRandom();
        private float meshMeshArea;
        public string MeshName;
        private static float[] ZeroCSpectrumArray = SampledSpectrum.ZeroSpectrum().ToArray();
        private float[] RgbSpectrumZeroArray = RgbSpectrum.ZeroSpectrum().ToArray();


        private float[] lightSpectra;
        private float meshArea;
        public string Name
        {
            get { return MeshName; }        
        }

        internal MeshLight()
        {

        }

        public MeshLight(TriangleMesh m, RgbSpectrum gain = new RgbSpectrum())
        {
            this.mesh = m;
            this.MeshName = mesh.MeshName;
            this.gain = gain;
            this.LightName = this.MeshName;
            meshArea = this.mesh.triangles.Sum(item => item.AreaV(ref mesh.scene.Vertices));

        }

        public MeshLight(FrameLightsource lightsource)
        {
            MeshName = lightsource.Parameters.Get<string>(FrameLightsource.AreaLightGeometryName);
            LightName = MeshName;
            spectra = SPD_Data.FromSampled(SPD_Data.A, SpectrumType.Illuminant);
            gain = lightsource.Parameters.Get<RgbSpectrum>(FrameLightsource.LightsourcePower);
        }

        protected Triangle SampleTriangle()
        {
            return this.mesh.triangles[rnd.Next(0, mesh.triangles.Count - 1)];
        }

        protected Triangle SampleTriangle(float f)
        {
            var index = (int)(f * (mesh.triangles.Count-1));
            return mesh.triangles[index];
        }

        #region ILight Members

        public bool IsDelta
        {
            get
            {
                return false;
            }
        }

        public RgbSpectrum Le(Vector dir)
        {
            var sampleN = NormalModifier*SampleTriangle().ComputeNormal(ref mesh.scene.Vertices);

            if (Normal.Dot(ref sampleN, ref dir) <= 0f)
            {
                return RgbSpectrum.ZeroSpectrum();
            }

            return gain; // Light sources are supposed to have flat color
        }

        public RgbSpectrum Sample(ref Point point, ref Normal n, float u0, float u1, float u2, out RayInfo ray, out float pdf)
        {
            var samplePoint = new Point();
            float b0, b1, b2;
            int tries = 0;
            int maxTries = 0;
        startTry:
            var tri = SampleTriangle();

            tri.Sample(ref mesh.scene.Vertices, u2, u1, out samplePoint, out b0, out b1, out b2);
            var TriangleNormal =NormalModifier * tri.ComputeNormal(ref mesh.scene.Vertices);
            var area = tri.AreaV(ref mesh.scene.Vertices);
            //var sampleN = TriangleNormal;
            //var N = n;

            Vector wi = samplePoint - point;
            //wi.Normalize();
            float distanceSquared = wi.Length2();
            float distance = MathLab.Sqrt(distanceSquared);
            wi /= distance;

            var nw = -wi;
            float sampleNdotMinusWi = Normal.Dot(ref TriangleNormal, ref nw);
            float NdotWi = Normal.Dot(ref n, ref wi);
            if ((sampleNdotMinusWi <= 0f) || (NdotWi <= 0f))
            {
                tries++;
                if (tries > maxTries)
                {
                    pdf = 0f;
                    ray = new RayInfo(Point.Zero, Vector.Zero);
                    return RgbSpectrum.ZeroSpectrum();
                }

                goto startTry;
            }

            ray = new RayInfo(point, wi, MathLab.RAY_EPSILON, distance - MathLab.RAY_EPSILON);
            pdf = (distanceSquared / (sampleNdotMinusWi * meshArea))  * (1f / Math.Max(1, tries + 1));

            // Using 0.01 instead of 0.0 to cut down fireflies
            if (pdf <= 0.01f)
            {
                pdf = 0f;
                return RgbSpectrum.ZeroSpectrum();
            }
            return gain * meshArea;
        }

        public RgbSpectrum SamplePhoton(SceneGeometryInfo scene, float u0, float u1, float u2, float u3, float u4, out float pdf,
                                        out RayInfo ray)
        {
            float b0, b1, b2;
            Point orig;
            var tri = SampleTriangle(u4);

            tri.Sample(ref mesh.scene.Vertices, u0, u1, out orig, out b0, out b1, out b2);
            var TriangleNormal = NormalModifier * tri.ComputeNormal(ref mesh.scene.Vertices);
            var area = tri.AreaV(ref mesh.scene.Vertices);
            // Ray direction
            var sampleN = TriangleNormal;
            Vector dir = MC.UniformSampleSphere(u2, u3);
            float RdotN = Normal.Dot(ref dir, ref sampleN);
            if (RdotN < 0f)
            {
                dir *= -1f;
                RdotN = -RdotN;
            }

            ray = new RayInfo(orig, dir);

            pdf = (MathLab.INVTWOPI / area) * (1f / mesh.TrianglesCount);

            return (gain * RdotN);
        }

        #endregion
    }
}